#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
多线程数据库同步工具主程序

此程序实现了一个多线程框架，用于并行同步数据库表数据。主要功能包括：
- 线程池管理（可配置最大线程数）
- 任务队列处理
- 异常捕获和重试机制
- 优雅退出处理（支持Ctrl+C）
- 线程安全的日志记录
"""

import os
import random
import signal
import sys
import threading
import time
import concurrent.futures
from typing import List, Dict, Any

# 导入自定义模块
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from lib.logger import Logger
from lib.task_manager import TaskManager
from lib.db_operations import TimeoutError, mock_fetch_data, mock_insert_data
from config import Config


class DBSyncTool:
    """
    数据库同步工具类
    
    负责管理整个数据库同步过程，包括初始化线程池、分发任务、处理异常等
    """
    
    def __init__(self, config: Config):
        """
        初始化数据库同步工具
        
        Args:
            config: 配置对象，包含线程数、重试次数等配置项
        """
        self.config = config
        self.logger = Logger(config.log_file, config.log_level)
        self.task_manager = TaskManager()
        self.stop_event = threading.Event()  # 用于信号处理和优雅退出
        self.force_stop_event = threading.Event()  # 用于强制终止所有线程
        self.error_occurred = threading.Event()  # 用于标记是否发生错误
        self.success_count = 0  # 成功同步的表数量
        self.failed_count = 0   # 失败的表数量
        self.cancelled_count = 0  # 被取消的表数量
        self.total_count = 0    # 总表数量
        self.running_threads = set()  # 跟踪正在运行的线程
        self.running_threads_lock = threading.Lock()  # 线程安全地访问running_threads
        self._setup_signal_handlers()
    
    def _setup_signal_handlers(self):
        """设置信号处理器，捕获Ctrl+C等信号"""
        def signal_handler(signum, frame):
            """处理中断信号"""
            if not self.stop_event.is_set():
                # 第一次收到信号，尝试优雅退出
                self.logger.info(f"接收到信号 {signum}，准备停止程序...")
                self.logger.info("按一次Ctrl+C准备优雅退出，再按一次Ctrl+C将强制终止所有线程...")
                self.stop_event.set()  # 设置停止标志
                # 设置定时器，如果3秒后程序仍在运行，则强制退出
                def force_stop_timer():
                    if not self.force_stop_event.is_set():
                        self.logger.warning("优雅退出超时，将强制终止所有线程...")
                        self.force_stop_program()
                timer = threading.Timer(3, force_stop_timer)
                timer.daemon = True  # 确保定时器不会阻止程序退出
                timer.start()
            else:
                # 第二次收到信号，强制退出
                self.logger.warning("接收到第二次中断信号，立即强制终止所有线程...")
                self.force_stop_program()
        
        # 捕获SIGINT (Ctrl+C) 和 SIGTERM 信号
        signal.signal(signal.SIGINT, signal_handler)
        signal.signal(signal.SIGTERM, signal_handler)
    
    def force_stop_program(self):
        """强制停止程序
        
        当接收到强制停止信号（如Ctrl+C两次）时调用此方法
        立即终止所有线程并退出程序
        """
        self.logger.warning("接收到强制停止信号，准备终止所有线程并退出程序...")
        
        # 设置强制停止标志
        self.force_stop_event.set()
        self.stop_event.set()
        
        # 等待正在运行的线程完成，但有时间限制
        start_wait_time = time.time()
        max_wait_time = 5  # 最多等待5秒
        
        while time.time() - start_wait_time < max_wait_time:
            with self.running_threads_lock:
                active_threads = len(self.running_threads)
            
            if active_threads > 0:
                self.logger.warning(f"仍有 {active_threads} 个线程正在运行，将强制退出")
            else:
                break
            
            time.sleep(0.1)
        else:
            # 如果超时，记录日志
            with self.running_threads_lock:
                active_threads = len(self.running_threads)
            
            if active_threads > 0:
                self.logger.warning(f"仍有 {active_threads} 个线程正在运行，将强制退出")
        
        # 在实际应用中，如果需要强制终止线程，可以考虑以下方法：
        # 1. 使用ctypes调用系统API终止线程（不推荐，可能导致资源泄露）
        # 2. 使用超时机制包装数据库操作
        # 3. 使用非阻塞I/O
        
        # 这里我们选择直接退出程序，这会导致所有线程被强制终止
        if os.name == 'nt':  # Windows系统
            # 在Windows上，直接退出进程
            self.logger.info("在Windows系统上强制终止进程...")
            os._exit(1)
        else:  # Unix/Linux系统
            # 在Unix/Linux上，发送SIGKILL信号给自己
            self.logger.info("在Unix/Linux系统上发送SIGKILL信号...")
            os.kill(os.getpid(), signal.SIGKILL)
    
    def _create_task_list(self) -> List[str]:
        """
        创建任务列表（模拟500张表）
        
        Returns:
            List[str]: 表名列表
        """
        # 模拟500张表
        return [f"table_{i:04d}" for i in range(1, 501)]
    
    def sync_table(self, table_name: str) -> Dict[str, Any]:
        """
        同步单个表的函数，作为线程池中的任务
        
        Args:
            table_name: 要同步的表名
            
        Returns:
            Dict[str, Any]: 任务执行结果，包含状态、表名、耗时等信息
        """
        thread_id = threading.get_ident()
        thread_name = threading.current_thread().name
        start_time = time.time()
        
        # 注册当前线程为运行中
        with self.running_threads_lock:
            self.running_threads.add((thread_id, thread_name))
        
        try:
            result = {
                'table': table_name,
                'status': 'failed',
                'thread_id': thread_id,
                'thread_name': thread_name,
                'start_time': start_time,
                'end_time': None,
                'duration': None,
                'error': None,
                'retries': 0
            }
            
            # 检查是否需要停止
            if self.stop_event.is_set() or self.force_stop_event.is_set():
                result['error'] = "任务被中断（程序即将退出）"
                self.logger.warning(f"表 {table_name} 同步被中断")
                return result
            
            self.logger.info(f"开始同步表: {table_name}")
            
            # 检查强制停止标志
            if self.force_stop_event.is_set():
                self.logger.warning(f"检测到强制停止信号，立即停止表 {table_name} 的同步")
                result['error'] = "任务被强制终止"
                return result
            
            try:
                # 使用带超时的数据库操作包装器
                data = self._execute_with_timeout(mock_fetch_data, (table_name,), timeout=10)
                
                # 检查是否需要停止
                if self.stop_event.is_set() or self.force_stop_event.is_set():
                    result['error'] = "任务被中断（程序即将退出）"
                    self.logger.warning(f"表 {table_name} 同步被中断")
                    return result
                
                # 使用带超时的数据库操作包装器
                self._execute_with_timeout(mock_insert_data, (table_name, data), timeout=10)
                
                # 计算耗时
                end_time = time.time()
                duration = end_time - start_time
                
                result.update({
                    'status': 'success',
                    'end_time': end_time,
                    'duration': duration,
                    'error': None
                })
                
                self.logger.info(f"表 {table_name} 同步成功，耗时: {duration:.2f}秒")
                return result
                
            except Exception as e:
                # 记录错误
                end_time = time.time()
                duration = end_time - start_time
                result.update({
                    'status': 'failed',
                    'end_time': end_time,
                    'duration': duration,
                    'error': str(e)
                })
                self.logger.error(f"表 {table_name} 同步失败: {str(e)}")
                return result
        finally:
            # 无论如何都要从运行线程集合中移除当前线程
            with self.running_threads_lock:
                self.running_threads.discard((thread_id, thread_name))
    
    def _execute_with_timeout(self, func, args, timeout):
        """
        执行函数并设置超时
        
        Args:
            func: 要执行的函数
            args: 函数参数
            timeout: 超时时间（秒）
            
        Returns:
            函数的返回值
            
        Raises:
            TimeoutError: 如果执行超时
            其他异常: 原始函数可能抛出的异常
        """
        # 结果存储和异常存储
        result = [None]
        exception = [None]
        completed = [False]
        
        # 工作线程函数
        def worker():
            try:
                result[0] = func(*args)
                completed[0] = True
            except Exception as e:
                exception[0] = e
        
        # 创建并启动工作线程
        worker_thread = threading.Thread(target=worker)
        worker_thread.daemon = True  # 设置为守护线程，确保主程序退出时线程会被终止
        worker_thread.start()
        
        # 等待工作线程完成，但定期检查停止标志
        start_time = time.time()
        while time.time() - start_time < timeout:
            # 检查是否有强制停止信号
            if self.force_stop_event.is_set():
                raise TimeoutError(f"执行 {func.__name__} 被强制终止")
            
            # 检查工作线程是否完成
            if not worker_thread.is_alive():
                break
            
            # 短暂休眠，避免CPU占用过高
            time.sleep(0.1)
        
        # 如果工作线程仍在运行，并且超时了
        if worker_thread.is_alive():
            # 在Python中无法安全地终止线程，但我们可以设置强制停止标志
            # 实际应用中，更好的方法是使用支持超时的数据库连接
            self.logger.warning(f"执行 {func.__name__} 超时，将继续等待但不再阻塞主线程退出")
            raise TimeoutError(f"执行 {func.__name__} 超时（{timeout}秒）")
        
        # 如果有异常，重新抛出
        if exception[0] is not None:
            raise exception[0]
        
        # 返回结果
        return result[0]
    
    def process_result(self, future: concurrent.futures.Future):
        """
        处理线程池任务的结果
        
        Args:
            future: 任务的Future对象
        """
        try:
            # 检查future是否被取消
            if future.cancelled():
                self.logger.warning("任务已被取消，跳过结果处理")
                self.cancelled_count += 1
                return
                
            result = future.result()
            
            # 更新统计信息
            if result and isinstance(result, dict):
                if result.get('status') == 'success':
                    self.success_count += 1
                else:
                    self.failed_count += 1
                    # 如果不忽略错误，设置错误标志
                    if not self.config.ignore_errors:
                        self.error_occurred.set()
                        self.logger.error(f"表 {result.get('table')} 同步失败，由于未设置ignore_errors，程序将在完成当前任务后退出")
                
                # 输出进度
                processed = self.success_count + self.failed_count + self.cancelled_count
                if processed % 10 == 0 or processed == self.total_count:
                    self.logger.info(f"进度: {processed}/{self.total_count}, 成功: {self.success_count}, 失败: {self.failed_count}, 取消: {self.cancelled_count}")
            else:
                self.logger.warning("任务返回无效结果")
                self.failed_count += 1
                # 如果不忽略错误，设置错误标志
                if not self.config.ignore_errors:
                    self.error_occurred.set()
                    self.logger.error("任务返回无效结果，由于未设置ignore_errors，程序将在完成当前任务后退出")
                
        except concurrent.futures.CancelledError:
            self.logger.warning("任务被取消")
            self.cancelled_count += 1
            # 取消不算作错误，不设置error_occurred标志
        except Exception as e:
            self.logger.error(f"处理任务结果时发生异常: {str(e)}")
            import traceback
            self.logger.error(f"异常堆栈: {traceback.format_exc()}")
            self.failed_count += 1
            # 如果不忽略错误，设置错误标志
            if not self.config.ignore_errors:
                self.error_occurred.set()
                self.logger.error(f"处理任务结果时发生异常，由于未设置ignore_errors，程序将在完成当前任务后退出")
    
    def run(self):
        """
        运行数据库同步工具的主方法
        """
        try:
            self.logger.info("开始数据库表同步任务")
            self.logger.info(f"配置信息 - 最大线程数: {self.config.max_workers}")
            
            # 创建任务列表
            tables = self._create_task_list()
            self.total_count = len(tables)
            self.logger.info(f"共有 {self.total_count} 张表需要同步")
            
            start_time = time.time()
            
            # 创建线程池
            # 创建线程池，并设置线程名称前缀为'work-'，使线程在日志中显示为work-0, work-1等格式
            with concurrent.futures.ThreadPoolExecutor(max_workers=self.config.max_workers,thread_name_prefix='work') as executor:
                self.logger.info(f"线程池已创建，最大线程数: {self.config.max_workers}")
                
                # 提交所有任务
                future_to_table = {}
                for table in tables:
                    # 检查是否需要停止
                    if self.stop_event.is_set() or self.force_stop_event.is_set():
                        self.logger.warning("检测到停止信号，不再提交新任务")
                        break
                    
                    future = executor.submit(self.sync_table, table)
                    future.add_done_callback(self.process_result)
                    future_to_table[future] = table
                
                # 等待所有任务完成，但定期检查是否需要停止
                while future_to_table:
                    # 检查是否需要强制停止
                    if self.force_stop_event.is_set():
                        self.logger.warning("检测到强制停止信号，立即退出程序")
                        # 在这里我们不尝试取消任务，因为强制停止事件已经设置
                        # 程序将在退出线程池上下文管理器时立即退出
                        break
                    
                    # 检查是否需要优雅停止或发生错误且不忽略错误
                    if self.stop_event.is_set() or (self.error_occurred.is_set() and not self.config.ignore_errors):
                        reason = "程序正在停止" if self.stop_event.is_set() else "检测到错误且未设置ignore_errors"
                        self.logger.warning(f"{reason}，取消所有未完成的任务")
                        cancelled_count = 0
                        for future in list(future_to_table.keys()):
                            if not future.done():
                                cancelled = future.cancel()
                                if cancelled:
                                    table_name = future_to_table[future]
                                    self.logger.info(f"已取消表 {table_name} 的同步任务")
                                    cancelled_count += 1
                                    del future_to_table[future]
                        self.logger.info(f"共取消了 {cancelled_count} 个任务")
                        
                        # 如果仍有任务在运行，等待一段时间后如果还未完成，则强制退出
                        if future_to_table:
                            self.logger.warning(f"仍有 {len(future_to_table)} 个任务在运行中")
                            wait_start = time.time()
                            wait_timeout = 2  # 等待2秒
                            
                            while future_to_table and time.time() - wait_start < wait_timeout:
                                # 检查是否有强制停止信号
                                if self.force_stop_event.is_set():
                                    break
                                    
                                try:
                                    # 短暂等待任务完成
                                    done, _ = concurrent.futures.wait(
                                        future_to_table.keys(), 
                                        timeout=0.2,  # 200毫秒超时
                                        return_when=concurrent.futures.FIRST_COMPLETED
                                    )
                                    
                                    # 移除已完成的任务
                                    for future in done:
                                        if future in future_to_table:
                                            del future_to_table[future]
                                except Exception as e:
                                    self.logger.error(f"等待任务完成时发生异常: {str(e)}")
                        break
                    
                    # 检查是否有任务完成
                    try:
                        # 如果发生错误且不忽略错误，设置较短的超时时间以便更快退出
                        wait_timeout = 0.5 if (self.error_occurred.is_set() and not self.config.ignore_errors) else 1
                        done, _ = concurrent.futures.wait(
                            future_to_table.keys(), 
                            timeout=wait_timeout,  # 根据是否发生错误调整超时时间
                            return_when=concurrent.futures.FIRST_COMPLETED
                        )
                        
                        # 移除已完成的任务
                        for future in done:
                            if future in future_to_table:
                                del future_to_table[future]
                    except Exception as e:
                        self.logger.error(f"等待任务完成时发生异常: {str(e)}")
                        # 继续循环，避免因为异常而完全退出等待
            
            end_time = time.time()
            total_duration = end_time - start_time
            
            # 输出最终统计信息
            self.logger.info("数据库表同步任务已完成")
            self.logger.info(f"总耗时: {total_duration:.2f}秒")
            self.logger.info(f"成功同步: {self.success_count} 张表")
            self.logger.info(f"同步失败: {self.failed_count} 张表")
            self.logger.info(f"任务取消: {self.cancelled_count} 张表")
            
            # 根据是否忽略错误和是否有失败任务决定返回码
            if self.failed_count > 0 and not self.config.ignore_errors:
                return 1
            return 0
            
        except Exception as e:
            self.logger.error(f"程序运行过程中发生未捕获的异常: {str(e)}")
            return 2
        finally:
            self.logger.info("程序已退出")


def main():
    """主函数"""
    # 加载配置
    config = Config()
    
    # 创建并运行同步工具
    sync_tool = DBSyncTool(config)
    sys.exit(sync_tool.run())


if __name__ == "__main__":
    main()